﻿using Microsoft.Practices.Prism.Commands;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using System.Windows.Data;

namespace HobSoft.SoccerBet2014.Client.ViewModels
{
    using Common;
    using Domain.ClientService;
    using SoccerBet2014.Common.Wpf.Data;
    using dm = Domain.Models;
    [Serializable]
    public class BetShellModel : CommandViewModel
    {
        #region command
        public ICommand OrderOddsCommand
        {
            get;
            protected set;
        }
        public ICommand ShowOrderWindowCommand { get; protected set; }
        public ICommand ShowAccountWindowCommand { get; protected set; }
        public ICommand QueryOddsAutoStartCommand { get; protected set; }
        public ICommand ChangeOddsCommand { get; protected set; }
        public ICommand ChangeOddsPageCommand { get; protected set; }
        public ICommand CancelBetInfoCommand { get; protected set; }
        public ICommand QueryOrderCommand { get; protected set; }
        public ICommand QueryBfAndBetInfoDataCommand { get; protected set; }
        public ICommand CancelBfGainCommand { get; protected set; }
        public ICommand BmBettingCommand { get; protected set; }
        public ICommand SetBettingFocusCommand { get; protected set; }
        #endregion

        #region public models property
        private ConfigurationShellModel _configShellModel;
        public ConfigurationShellModel ConfigShellModel
        {
            get { return _configShellModel; }
            set
            {
                PropertyValueChange(ref _configShellModel, value, "ConfigShellModel");
            }
        }

        private ObservableCollection<BfEvent> _bfEventModels;
        public ObservableCollection<BfEvent> BfEventModels
        {
            get { return _bfEventModels; }
            set { PropertyValueChange(ref _bfEventModels, value, "BfEventModels");}
        }

        private ObservableCollection<Game> _gameModels;
        public ObservableCollection<Game> GameModels
        {
            get { return _gameModels; }
            set
            {
                PropertyValueChange(ref _gameModels, value, "GameModels");
            }
        }

        private ObservableCollection<OddsGroup> _oddsModels;
        public ObservableCollection<OddsGroup> OddsModels
        {
            get { return _oddsModels; }
            set
            {
                PropertyValueChange(ref _oddsModels, value, "OddsModels");
            }
        }

        private ICollectionView _oddsModelsView;
        public ICollectionView OddsModelsView
        {
            get { return _oddsModelsView; }
            set
            {
                PropertyValueChange(ref _oddsModelsView, value, "OddsModelsView");
            }
        }

        private IList<int> _oddsModelsPage;
        public IList<int> OddsModelsPage
        {
            get
            {
                return _oddsModelsPage;
            }
            set
            {
                PropertyValueChange(ref _oddsModelsPage, value, "OddsModelsPage");
            }
        }

        private dm.Odds.MarketType _currOddsType;
        public dm.Odds.MarketType CurrOddsType
        {
            get { return _currOddsType; }
            set
            {
                PropertyValueChange(ref _currOddsType, value, "CurrOddsType");
            }
        }

        private OddsGroup _currOddsGame;
        public OddsGroup CurrOddsGame
        {
            get { return _currOddsGame; }
            set
            {
                PropertyValueChange(ref _currOddsGame, value, "CurrOddsGame");
            }
        }

        private BfEvent _market;
        public BfEvent BfMarket
        {
            get { return _market; }
            set
            {
                PropertyValueChange(ref _market, value, "BfMarket");
            }
        }

        private bool _hasIbcInfo = false;
        public bool HasIbcInfo
        {
            get { return _hasIbcInfo; }
            set
            {
                PropertyValueChange(ref _hasIbcInfo, value, "HasIbcInfo");
            }
        }

        private bool _hasSboInfo = false;
        public bool HasSboInfo
        {
            get { return _hasSboInfo; }
            set
            {
                PropertyValueChange(ref _hasSboInfo, value, "HasSboInfo");
            }
        }

        private bool _hasHgInfo = false;
        public bool HasHgInfo
        {
            get { return _hasHgInfo; }
            set
            {
                PropertyValueChange(ref _hasHgInfo, value, "HasHgInfo");
            }
        }

        private BetInfo _ibcBetInfo;
        public BetInfo IbcBetInfo
        {
            get { return _ibcBetInfo; }
            set
            {
                PropertyValueChange(ref _ibcBetInfo, value, "IbcBetInfo");
                HasIbcInfo = (_ibcBetInfo != null ? true : false);
            }
        }

        private BetInfo _sboBetInfo;
        public BetInfo SboBetInfo
        {
            get { return _sboBetInfo; }
            set
            {
                PropertyValueChange(ref _sboBetInfo, value, "SboBetInfo");
                HasSboInfo = (_sboBetInfo != null ? true : false);
            }
        }

        private BetInfo _hgBetInfo;
        public BetInfo HgBetInfo
        {
            get { return _hgBetInfo; }
            set
            {
                PropertyValueChange(ref _hgBetInfo, value, "HgBetInfo");
                HasHgInfo = (_hgBetInfo != null ? true : false);
            }
        }
        #endregion

        #region state and setting
        public int OddsViewPerPage
        {
            get
            {
                return Properties.Settings.Default.VM_BetShellModel_OddsPerPage;
            }
        }

        private double _queryOddsInterval = 0;
        public double QueryOddsInterval
        {
            get
            {
                if (_queryOddsInterval < 1)
                    _queryOddsInterval = Properties.Settings.Default.VM_BetShellModel_RefreshOdds;
                return _queryOddsInterval;
            }
            set
            {
                if (_queryOddsInterval < 1)
                    _queryOddsInterval = Properties.Settings.Default.VM_BetShellModel_RefreshOdds;
                else
                    _queryOddsInterval = value;
                queryOddsTimer.Interval = TimeSpan.FromMilliseconds(_queryOddsInterval);
            }
        }

        protected bool _onQueryOdds = false;
        public bool OnQueryOdds
        {
            get
            {
                return _onQueryOdds;
            }
            set
            {
                PropertyValueChange(ref _onQueryOdds, value, "OnQueryOdds");
            }
        }
        #endregion

        #region cort &  initialize
        protected ConcurrentDictionary<byte, BetInfo> betInfoViewCollection;

        protected DateTime? _oddsLastUpdate;
        protected bool _cancelQueryOdds = false;
        protected bool _enableAutoQueryOdds = false;
        protected DispatcherTimer queryOddsTimer;
        protected DispatcherTimer queryBetInfoTimer;
        protected SortDescription _startTimeOrder = new SortDescription("Game.StartTime", ListSortDirection.Ascending);
        protected SortDescription _bfEventIDOrder = new SortDescription("BfEventID", ListSortDirection.Ascending);
        protected SortDescription _handicapOrder = new SortDescription("Handicap", ListSortDirection.Ascending);

        private readonly Factory.IViewModelFactory viewModelFactory;
        private readonly Factory.IWindowFactory windowFactory;
        private readonly IBetCustomDataService betCustomDatatService;
        private readonly IBfCustomDataService bfCustomDataService;
        private readonly IBetSiteProxyService betSiteProxyService;
        private readonly IBfRequestService bfRequestService;

        public BetShellModel(Factory.IViewModelFactory _viewModelFactory
                                        , Factory.IWindowFactory _windowFactory
                                        , IBetSiteProxyService _betSiteProxyService
                                        , IBetCustomDataService _betCustomDatatService
                                        , IBfCustomDataService _bfCustomDataService
                                        , IBfRequestService _bfRequestService)
        {
            viewModelFactory = _viewModelFactory;
            windowFactory = _windowFactory;
            betSiteProxyService = _betSiteProxyService;
            betCustomDatatService = _betCustomDatatService;
            bfCustomDataService = _bfCustomDataService;
            bfRequestService = _bfRequestService;
            InitializeCommand();
            InitializeModels();
        }

        protected void InitializeCommand()
        {
            OrderOddsCommand = new DelegateCommand<string>(OrderOdds, CanOrderOdds);
            ChangeOddsCommand = new DelegateCommand<dm.Odds.MarketType?>(QueryOdds, CanQueryOdds);
            ChangeOddsPageCommand = new DelegateCommand<int?>(ChangeOddsPage, CanChangeOddsPage);
            QueryOddsAutoStartCommand = new DelegateCommand<bool?>(AutoQueryOdds);
            QueryBfAndBetInfoDataCommand = new DelegateCommand<OddsGroup>(QueryBfData);
            CancelBetInfoCommand = new DelegateCommand<byte?>(CancelBetInfo);
            BmBettingCommand = new DelegateCommand<decimal?>(BmBetting, CanBmBetting);
            SetBettingFocusCommand = new DelegateCommand<byte?>(SetBettingFocus);
            //QueryOrderCommand = new DelegateCommand(QueryOrder);
            //CancelBfGainCommand = new DelegateCommand(CancelBlance);

            RegisterCommand(OrderOddsCommand, "Odds排序");
            RegisterCommand(ChangeOddsCommand, "Odds类型变更");
            RegisterCommand(ChangeOddsPageCommand, "Odds分页查询");
            RegisterCommand(QueryOddsAutoStartCommand, "自动查询Odds");
            RegisterCommand(QueryBfAndBetInfoDataCommand, "查询BetInfo和Bf数据");
            RegisterCommand(CancelBetInfoCommand, "取消BetInfo");
            RegisterCommand(BmBettingCommand, "投注焦点博彩网站");
            RegisterCommand(SetBettingFocusCommand, "设置投注焦点");
        }

        protected void InitializeModels()
        {
            queryOddsTimer = new DispatcherTimer();
            queryOddsTimer.Tick += QueryOddsTimer_Tick;
            queryOddsTimer.Interval = TimeSpan.FromMilliseconds(QueryOddsInterval);

            CurrOddsType = dm.Odds.MarketType.GOU;
            betInfoViewCollection = new ConcurrentDictionary<byte, BetInfo>();
            GameModels = new ObservableCollection<Game>();
            OddsModels = new ObservableCollection<OddsGroup>();
            BfEventModels = new ObservableCollection<BfEvent>();

            OddsModelsView = new ListCollectionView(OddsModels);
            ((INotifyPropertyChanged)OddsModelsView).PropertyChanged += OddsModelsView_PropertyChanged;
            OddsModelsView.SortDescriptions.Add(_startTimeOrder);
            OddsModelsView.SortDescriptions.Add(_bfEventIDOrder);
            OddsModelsView.SortDescriptions.Add(_handicapOrder);
            ConfigShellModel = viewModelFactory.CreateOrGet<ConfigurationShellModel>();

            BackgroundWorker bWorker = new BackgroundWorker();
            State = ModelState.Initializing;
            bWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler((s, e) =>
            {
                bWorker.Dispose();
            });
            bWorker.DoWork += new DoWorkEventHandler((s, e) =>
            {
                BetApplication.Current.Dispatcher.Invoke(() =>
                {
                    OnQueryOdds = true;
                });
                QueryOdds(CurrOddsType, false);
                BetApplication.Current.Dispatcher.Invoke(() =>
                {
                    OnQueryOdds = false;
                    AutoQueryOdds(true);
                    State = ModelState.Freeing;
                    RaiseCommandCanExecute<dm.Odds.MarketType?>(ChangeOddsCommand);
                    RaiseCommandCanExecute<string>(OrderOddsCommand);
                    RaiseCommandCanExecute<int?>(ChangeOddsPageCommand);
                });
            });
            bWorker.RunWorkerAsync();

        }
        #endregion

        #region event handler

        private void OddsModelsView_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "PageCount")
            {
                var view = OddsModelsView as PagingCollectionView;
                OddsModelsPage = Enumerable.Range(1, view.PageCount).ToList();
            }
        }

        protected void QueryBetInfoTimer_Tick(object sender, EventArgs e)
        {
            queryBetInfoTimer.Stop();
        }

        protected void QueryOddsTimer_Tick(object sender, EventArgs e)
        {
            if (_onQueryOdds) return;
            queryOddsTimer.Stop();
            OnQueryOdds = true;

            Task.Factory.StartNew(() => QueryOdds(CurrOddsType, true)).ContinueWith(t =>
            {
                BetApplication.Current.Dispatcher.Invoke(new Action(delegate()
                    {
                        if (_enableAutoQueryOdds)
                            queryOddsTimer.Start();
                        OnQueryOdds = false;
                    }));
            });

        }

        private void OddsGroup_OnQueryBfDataOver(OddsGroup group, BfEvent bfevent)
        {
            if (BfMarket != null && BfMarket != bfevent)
            {
                BfMarket.StopUpdateRunner();
                BfMarket.Dispose();
            }
            BfMarket = bfevent;
            BfMarket.PropertyChanged += BfMarket_PropertyChanged;
            BfMarket.StartUpdateRunner();
        }

        private void BfMarket_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "HasFocus")
            {
                foreach (var item in betInfoViewCollection.Values)
                {
                    item.IsFocus = false;
                }
            }
        }

        private void OddsGroup_OnQueryBetInfoBegin(object sender, byte bmId)
        {
            var group = sender as OddsGroup;
            switch (bmId)
            {
                case 10:
                    IbcBetInfo = null;
                    break;
                case 11:
                    SboBetInfo = null;
                    break;
                case 12:
                    HgBetInfo = null;
                    break;
                default:
                    break;
            }
            if (betInfoViewCollection.ContainsKey(bmId))
            {
                BetInfo oldBetInfo;
                betInfoViewCollection.TryRemove(bmId, out oldBetInfo);
                if (oldBetInfo != null)
                {
                    oldBetInfo.AutoUpdate(false);
                    oldBetInfo.PropertyChanged -= BetInfo_PropertyChanged;
                    oldBetInfo.Dispose();
                }
            }
        }

        private void OddsGroup_OnQueryBetInfoOver(OddsGroup group, BetInfo betInfo)
        {
            var bmId = betInfo.BmID;
            switch (bmId)
            {
                case 10:
                    IbcBetInfo = betInfo;
                    break;
                case 11:
                    SboBetInfo = betInfo;
                    break;
                case 12:
                    HgBetInfo = betInfo;
                    break;
                default:
                    break;
            }
            if (betInfoViewCollection.ContainsKey(bmId))
            {
                var oldBetInfo = betInfoViewCollection[bmId];
                if (oldBetInfo != null)
                {
                    oldBetInfo.AutoUpdate(false);
                    oldBetInfo.PropertyChanged -= BetInfo_PropertyChanged;
                    oldBetInfo.Dispose();
                }
                betInfoViewCollection[bmId] = betInfo;
            }
            else
            {
                betInfoViewCollection.TryAdd(bmId, betInfo);
            }

            betInfo.PropertyChanged += BetInfo_PropertyChanged;
        }

        private void BetInfo_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "IsFocus")
            {
                var betInfo = sender as BetInfo;
                if (betInfo == null) return;
                if (betInfo.IsFocus)
                {
                    foreach (var item in betInfoViewCollection.Values)
                    {
                        if (item != betInfo)
                        {
                            item.IsFocus = false;
                        }
                    }
                    if (BfMarket != null)
                        BfMarket.BlurRunner();
                }
            }
        }

        #endregion

        #region command imple
        protected bool CanChangeOddsPage(int? index)
        {
            return OnInitialized && State != ModelState.Initializing;
        }

        protected void ChangeOddsPage(int? index)
        {
            if (State == ModelState.Initializing) return;
            var view = OddsModelsView as PagingCollectionView;
            if (view != null)
            {
                view.CurrentPage = index.Value;
            }
        }

        protected bool CanOrderOdds(string order)
        {
            return OnInitialized && State != ModelState.Initializing;
        }

        protected void OrderOdds(string order)
        {
            if (State == ModelState.Initializing) return;
            switch (order)
            {
                case "StartTimeAsc":
                    _startTimeOrder.Direction = ListSortDirection.Ascending;
                    OddsModelsView.SortDescriptions[0] = _startTimeOrder;
                    break;
                case "StartTimeDesc":
                    _startTimeOrder.Direction = ListSortDirection.Descending;
                    OddsModelsView.SortDescriptions[0] = _startTimeOrder;
                    break;
                case "HandicapAsc":
                    _handicapOrder.Direction = ListSortDirection.Ascending;
                    OddsModelsView.SortDescriptions[2] = _handicapOrder;
                    break;
                case "HandicapDesc":
                    _handicapOrder.Direction = ListSortDirection.Descending;
                    OddsModelsView.SortDescriptions[2] = _handicapOrder;
                    break;
                default:
                    break;
            }
        }

        protected void SetBettingFocus(byte? bmId)
        {
            if (bmId == 10 && IbcBetInfo != null)
                IbcBetInfo.IsFocus = true;
            if (bmId == 11 && SboBetInfo != null)
                SboBetInfo.IsFocus = true;
            if (bmId == 12 && HgBetInfo != null)
                HgBetInfo.IsFocus = true;
        }

        protected bool CanBmBetting(decimal? money)
        {
            return IbcBetInfo != null || SboBetInfo != null || HgBetInfo != null || BfMarket != null;
        }
        protected void BmBetting(decimal? money)
        {
            bool onBetting = false;
            if (IbcBetInfo != null && IbcBetInfo.IsFocus)
            {
                IbcBetInfo.Money = money;
                IbcBetInfo.BetMoneyCommand.Execute(null);
                onBetting = true;
            }
            if (SboBetInfo != null && SboBetInfo.IsFocus)
            {
                SboBetInfo.Money = money;
                SboBetInfo.BetMoneyCommand.Execute(null);
                onBetting = true;
            }
            if (HgBetInfo != null && HgBetInfo.IsFocus)
            {
                HgBetInfo.Money = money;
                HgBetInfo.BetMoneyCommand.Execute(null);
                onBetting = true;
            }
            if (BfMarket != null && BfMarket.HasFocus)
            {
                BfMarket.FocusRunner.Transaction.Money = money.Value;
                BfMarket.FocusRunner.DoTransaction(money);
                onBetting = true;
            }
            if (!onBetting)
                MessageBox.Show("请设置投注焦点");
        }

        protected void AutoQueryOdds(bool? start)
        {
            if (start.HasValue && start.Value && !_enableAutoQueryOdds)
            {
                queryOddsTimer.Start();
                _enableAutoQueryOdds = true;
            }
            else
            {
                queryOddsTimer.Stop();
                _enableAutoQueryOdds = false;
            }
        }

        protected bool CanQueryOdds(dm.Odds.MarketType? type)
        {
            return OnInitialized && State != ModelState.Initializing;
        }

        protected void QueryOdds(dm.Odds.MarketType? type)
        {
            if (State == ModelState.Initializing) return;
            if (!type.HasValue) return;
            CurrOddsType = type.Value;

            OddsModels = new ObservableCollection<OddsGroup>();
            var perPage = Properties.Settings.Default.VM_BetShellModel_OddsPerPage;
            if (type.Value == dm.Odds.MarketType.G1X2 || type.Value == dm.Odds.MarketType.FH1X2)
                perPage = Properties.Settings.Default.VM_BetShellModel_Odds1X2PerPage;
            OddsModelsView = new ListCollectionView(OddsModels);
            ((INotifyPropertyChanged)OddsModelsView).PropertyChanged += OddsModelsView_PropertyChanged;
            OddsModelsView.SortDescriptions.Add(_startTimeOrder);
            OddsModelsView.SortDescriptions.Add(_bfEventIDOrder);
            OddsModelsView.SortDescriptions.Add(_handicapOrder);
            if (BfMarket != null)
            {
                BfMarket.StopUpdateRunner();
                BfMarket.Dispose();
                BfMarket = null;
            }
            foreach (var betInfo in betInfoViewCollection)
            {
                if (betInfo.Value != null)
                    betInfo.Value.AutoUpdate(false);
            }
            betInfoViewCollection.Clear();
            if (IbcBetInfo != null)
            {
                IbcBetInfo.Dispose();
                IbcBetInfo = null;
            }
            if (SboBetInfo != null)
            {
                SboBetInfo.Dispose();
                SboBetInfo = null;
            }
            if (HgBetInfo != null)
            {
                HgBetInfo.Dispose();
                HgBetInfo = null;
            }
            _cancelQueryOdds = true;
            Task.Factory.StartNew(() =>
            {
                QueryOdds(CurrOddsType, false);
            });
        }

        protected void QueryBfData(OddsGroup odds)
        {
            if (odds != null && odds != CurrOddsGame)
            {
                odds.QueryBfDataCommand.Execute(null);
                odds.QueryBetInfoCommand.Execute(BetItem.HomeOrOver);
            }
        }

        protected void CancelBetInfo(byte? bmId)
        {
            RaiseCommandStart(CancelBetInfoCommand);
            switch (bmId)
            {
                case 10:
                    if (IbcBetInfo != null)
                        IbcBetInfo.AutoUpdate(false);
                    IbcBetInfo = null;
                    break;
                case 11:
                    if (SboBetInfo != null)
                        SboBetInfo.AutoUpdate(false);
                    SboBetInfo = null;
                    break;
                case 12:
                    if (HgBetInfo != null)
                        HgBetInfo.AutoUpdate(false);
                    HgBetInfo = null;
                    break;
                default:
                    break;
            }
            RaiseCommandOver(CancelBetInfoCommand);
        }
        #endregion

        #region local method
        /// <summary>
        /// 建议使用后台线程访问
        /// </summary>
        /// <param name="type">请求类型</param>
        /// <param name="auto">是否来自自动更新</param>
        protected void QueryOdds(dm.Odds.MarketType type, bool auto)
        {
            RaiseCommandStart(QueryOddsAutoStartCommand, "开始查询Odds");
            var lastUpdate = DateTime.Now.AddMinutes(-150);

            IList<dm.BFEvent> bfEvents = null;
            IList<dm.GameMapping> mapping = null;
            IList<dm.Game> games = null;
            IList<dm.Odds> remoteOddsCollection = null;
            IList<dm.BFMarket> bfMarkets = null;

            var gmpTask = Task.Factory.StartNew(() => { mapping = bfCustomDataService.GetMapping(lastUpdate); });
            var bfeTask = Task.Factory.StartNew(() => { bfEvents = bfCustomDataService.GetLive(lastUpdate); });
            var mkTask = Task.Factory.StartNew(() => { bfMarkets = bfCustomDataService.GetLiveMarket(lastUpdate); });
            var gmodTask = Task.Factory.StartNew(() =>
            {
                //games = betCustomDatatService.GetLiveGames(lastUpdate);
                games = betCustomDatatService.GetEarlyGames();
            }).ContinueWith((t) =>
            {
                if (t.IsCanceled || t.Exception != null)
                {
                    if (t.Exception != null)
                    {
                        RaiseCommandError(t.Exception);
                        PublicLog.ErrorFormat("查询赔率列表，发生错误：{0}", t.Exception.InnerException.Message);
                    }
                    return;
                }
                if (games == null) return;
                var marketKeys = games.Select(g => dm.KeyHelper.KeyGenerateHelper.Create<dm.Market>(g.BmID, g.GameID, g.LastUpdateHost)).ToArray();
                var gamdIds = games.Select(g => g.GameID).ToArray();
                remoteOddsCollection = betCustomDatatService.GetTypeNewOdds(type, marketKeys);
            });

            Task.WaitAll(bfeTask, gmpTask, gmodTask, mkTask);
            if (bfEvents == null || mapping == null || games == null || remoteOddsCollection == null)
            {
                RaiseCommandOver(QueryOddsAutoStartCommand);
                return;
            }
            if (!bfEvents.Any() || !mapping.Any() || !games.Any() || !remoteOddsCollection.Any())
            {
                RaiseCommandOver(QueryOddsAutoStartCommand);
                return;
            }
            BetApplication.Current.Dispatcher.Invoke(new Action(delegate()
            {
                if (auto && _cancelQueryOdds)
                {
                    _cancelQueryOdds = false;
                    return;
                }
                _cancelQueryOdds = false;
                if (CurrOddsType != type) return;
                var hasNew = FillOddsCollection(OddsModels, bfEvents, bfMarkets, games, remoteOddsCollection, mapping, type);
                if (hasNew) this.OddsModelsView.Refresh();
                RaiseCommandOver(QueryOddsAutoStartCommand);
            }));
        }

        private bool FillOddsCollection(ObservableCollection<OddsGroup> fillOddModels
                                        , IList<dm.BFEvent> bfEvents
                                        , IList<dm.BFMarket> bfMarkets
                                        , IList<dm.Game> games
                                        , IList<dm.Odds> oddsRemoteCollection
                                        , IList<dm.GameMapping> mapping
                                        , dm.Odds.MarketType type)
        {
            var gameLastUpdate = games.Max(g => g.LastUpdateTime);
            var oddsLastUpdate = games.Max(o => o.LastUpdateTime);
            _oddsLastUpdate = gameLastUpdate > oddsLastUpdate ? gameLastUpdate : oddsLastUpdate;

            var handicapQuery = from e in bfEvents
                                join m in mapping on e.EventID equals m.EventID
                                join o in oddsRemoteCollection on new { m.BmID, m.GameID } equals new { o.BmID, o.GameID }
                                group e by new { e.EventID, o.Handicap } into hk
                                select new
                                {
                                    hk.Key.EventID,
                                    hk.Key.Handicap
                                };
            var array = handicapQuery.ToArray();

            var query = from e in bfEvents
                        join eh in handicapQuery on e.EventID equals eh.EventID
                        join m10 in mapping on new { e.EventID, BmID = (byte)10 } equals new { m10.EventID, m10.BmID } into m10join
                        from mj10 in m10join.DefaultIfEmpty(new dm.GameMapping { BmID = 10, GameID = "x" })
                        join m11 in mapping on new { e.EventID, BmID = (byte)11 } equals new { m11.EventID, m11.BmID } into m11join
                        from mj11 in m11join.DefaultIfEmpty(new dm.GameMapping { BmID = 11, GameID = "x" })
                        join m12 in mapping on new { e.EventID, BmID = (byte)12 } equals new { m12.EventID, m12.BmID } into m12join
                        from mj12 in m12join.DefaultIfEmpty(new dm.GameMapping { BmID = 12, GameID = "x" })
                        join g10 in games on new { mj10.BmID, mj10.GameID } equals new { g10.BmID, g10.GameID } into g10join
                        from gj10 in g10join.DefaultIfEmpty()
                        join g11 in games on new { mj11.BmID, mj11.GameID } equals new { g11.BmID, g11.GameID } into g11join
                        from gj11 in g11join.DefaultIfEmpty()
                        join g12 in games on new { mj12.BmID, mj12.GameID } equals new { g12.BmID, g12.GameID } into g12join
                        from gj12 in g12join.DefaultIfEmpty()
                        join o10 in oddsRemoteCollection on new { mj10.BmID, mj10.GameID, eh.Handicap } equals new { o10.BmID, o10.GameID, o10.Handicap } into o10join
                        from oj10 in o10join.DefaultIfEmpty()
                        join o11 in oddsRemoteCollection on new { mj11.BmID, mj11.GameID, eh.Handicap } equals new { o11.BmID, o11.GameID, o11.Handicap } into o11join
                        from oj11 in o11join.DefaultIfEmpty()
                        join o12 in oddsRemoteCollection on new { mj12.BmID, mj12.GameID, eh.Handicap } equals new { o12.BmID, o12.GameID, o12.Handicap } into o12join
                        from oj12 in o12join.DefaultIfEmpty()
                        orderby e.StartTime, e.EventID, eh.Handicap
                        select new
                        {
                            BfEvent = e,
                            Handicap = eh.Handicap,
                            Game10 = gj10,
                            Game11 = gj11,
                            Game12 = gj12,
                            Reversal10 = mj10.TeamReversal.HasValue ? mj10.TeamReversal.Value : false,
                            Odds10 = oj10,
                            Reversal11 = mj11.TeamReversal.HasValue ? mj11.TeamReversal.Value : false,
                            Odds11 = oj11,
                            Reversal12 = mj12.TeamReversal.HasValue ? mj12.TeamReversal.Value : false,
                            Odds12 = oj12
                        };

            bool hasNew = false;
            int index = -1;
            long lastTick = DateTime.Now.Ticks;
            foreach (var item in query)
            {
                if (CurrOddsType != type) return false;
                index++;
                var oGroup = fillOddModels.FirstOrDefault(og => og.BfEventID == item.BfEvent.EventID && og.Handicap == item.Handicap);
                var game = GameModels.FirstOrDefault(g => g.BfEventID == item.BfEvent.EventID);
                var bfEvent = BfEventModels.FirstOrDefault(be => be.EventID == item.BfEvent.EventID);
                if (game == null)
                {
                    game = viewModelFactory.CreateOrGet<Game>();
                    AutoMapper.Mapper.Map(item.BfEvent, game);
                    GameModels.Add(game);
                }
                game.Version = lastTick;
                if (string.IsNullOrEmpty(game.TimeState))
                    game.TimeState = game.StartTime.ToString("HH:mm");

                if (bfEvent == null)
                {
                    bfEvent = viewModelFactory.CreateOrGet<BfEvent>();
                    AutoMapper.Mapper.Map(item.BfEvent, bfEvent);
                    BfEventModels.Add(bfEvent);
                }
                bfEvent.Version = lastTick;
                var markets = bfMarkets.Where(bm => bm.EventID == bfEvent.EventID);
                foreach (var mk in markets)
                {
                    var vmMk = bfEvent.MarketModels.FirstOrDefault(m => m.MarketID == mk.MarketID);
                    if (vmMk == null)
                    {
                        vmMk = new BfMarket();
                        AutoMapper.Mapper.Map(mk, vmMk);
                        bfEvent.MarketModels.Add(vmMk);
                    }
                }

                if (oGroup == null)
                {
                    oGroup = viewModelFactory.CreateOrGet<OddsGroup>();
                    oGroup.BfEventID = item.BfEvent.EventID;
                    oGroup.Handicap = item.Handicap;
                    oGroup.Type = CurrOddsType;
                    oGroup.OnQueryBetInfoOver += OddsGroup_OnQueryBetInfoOver;
                    oGroup.OnQueryBfDataOver += OddsGroup_OnQueryBfDataOver;
                    oGroup.OnQueryBetInfoBegin += OddsGroup_OnQueryBetInfoBegin;
                    fillOddModels.Add(oGroup);
                    hasNew = true;
                }
                if (oGroup.BFRequestService == null)
                    oGroup.BFRequestService = bfRequestService;
                if (oGroup.Game == null || oGroup.Game != game)
                    oGroup.Game = game;
                if (oGroup.BfEvent == null || oGroup.BfEvent != bfEvent)
                    oGroup.BfEvent = bfEvent;

                oGroup.SortIndex = index;
                oGroup.Version = lastTick;

                if (item.Odds10 != null)
                {
                    if (oGroup.IbcOdds == null)
                        oGroup.IbcOdds = viewModelFactory.CreateOrGet<Odds>();
                    MappingToVMGameState(game, item.Game10, item.Reversal10);
                    MappingToVMOdds(oGroup.IbcOdds, item.Odds10, item.Reversal10);
                }
                else
                {
                    if (oGroup.IbcOdds != null)
                    {
                        oGroup.IbcOdds.Dispose();
                        oGroup.IbcOdds = null;
                    }
                }

                if (item.Odds11 != null)
                {
                    if (oGroup.SboOdds == null)
                        oGroup.SboOdds = viewModelFactory.CreateOrGet<Odds>();
                    if (item.Odds10 == null)
                        MappingToVMGameState(game, item.Game11, item.Reversal11);
                    MappingToVMOdds(oGroup.SboOdds, item.Odds11, item.Reversal11);
                }
                else
                {
                    if (oGroup.SboOdds != null)
                    {
                        oGroup.SboOdds.Dispose();
                        oGroup.SboOdds = null;
                    }
                }

                if (item.Odds12 != null)
                {
                    if (oGroup.HgOdds == null)
                        oGroup.HgOdds = viewModelFactory.CreateOrGet<Odds>();
                    if (item.Odds10 == null && item.Odds11 == null)
                    {
                        MappingToVMGameState(game, item.Game12, item.Reversal12);
                    }
                    MappingToVMOdds(oGroup.HgOdds, item.Odds12, item.Reversal12);
                }
                else
                {
                    if (oGroup.HgOdds != null)
                    {
                        oGroup.HgOdds.Dispose();
                        oGroup.HgOdds = null;
                    }
                }

                oGroup.ResetMaxPrice();
            }

            for (int i = 0; i < fillOddModels.Count; i++)
            {
                if (fillOddModels[i].Version != lastTick)
                    fillOddModels.RemoveAt(i);
            }
            for (int i = 0; i < GameModels.Count; i++)
            {
                if (GameModels[i].Version != lastTick)
                    GameModels.RemoveAt(i);
            }
            for (int i = 0; i < BfEventModels.Count; i++)
            {
                if (BfEventModels[i].Version != lastTick)
                    BfEventModels.RemoveAt(i);
            }
            return hasNew;
            //ResetOddsGroupOrder(fillOddModels);
        }


        protected void ResetOddsGroupOrder(ObservableCollection<OddsGroup> fillOddModels)
        {
            if (!fillOddModels.Any(o => o.SortIndex != fillOddModels.IndexOf(o)))
                return;

            for (int i = 0; i < fillOddModels.Count; i++)
            {
                var currOdds = fillOddModels[i];
                var oldIndex = fillOddModels.IndexOf(currOdds);
                fillOddModels.Move(oldIndex, (int)currOdds.SortIndex);
            }
        }

        protected void MappingToVMGameState(Game vmGame, dm.Game game, bool reversal)
        {
            if (!reversal)
            {
                vmGame.HomeScore = game.HomeScore;
                vmGame.AwayScore = game.AwayScore;
                vmGame.HomeRed = game.HomeRed;
                vmGame.AwayRed = game.AwayRed;
            }
            else
            {
                vmGame.HomeScore = game.AwayScore;
                vmGame.AwayScore = game.HomeScore;
                vmGame.HomeRed = game.AwayRed;
                vmGame.AwayRed = game.HomeRed;
            }
            if (game.State == dm.Game.GameState.InProgress)
                vmGame.TimeState = game.PlayeTime;
            vmGame.UpdateSourceBm = game.BmID;
            vmGame.CheckTimeState();
        }

        protected void MappingToVMOdds(Odds vmOdds, dm.Odds remoteOdds, bool reversal)
        {
            AutoMapper.Mapper.Map(remoteOdds, vmOdds);
            var homePrice = remoteOdds.Prices.FirstOrDefault(t => t.Type == dm.OddsPrice.OddsPriceType.Home || t.Type == dm.OddsPrice.OddsPriceType.Over);
            var awayPrice = remoteOdds.Prices.FirstOrDefault(t => t.Type == dm.OddsPrice.OddsPriceType.Away || t.Type == dm.OddsPrice.OddsPriceType.Under);
            var tiePrice = remoteOdds.Prices.FirstOrDefault(t => t.Type == dm.OddsPrice.OddsPriceType.Tie);
            //大小球不做位置交换
            if (reversal && remoteOdds.Type != dm.Odds.MarketType.FHOU && remoteOdds.Type != dm.Odds.MarketType.GOU)
            {
                vmOdds.Reversal = true;
                vmOdds.HValue = awayPrice.Price;
                vmOdds.AValue = homePrice.Price;
            }
            else
            {
                vmOdds.Reversal = false;
                vmOdds.HValue = homePrice.Price;
                vmOdds.AValue = awayPrice.Price;
            }
            if (vmOdds.Type == dm.Odds.MarketType.G1X2 || vmOdds.Type == dm.Odds.MarketType.FH1X2)
                vmOdds.OValue = tiePrice != null ? tiePrice.Price : 0m;
            else
                vmOdds.OValue = remoteOdds.Handicap;
            if (vmOdds.SiteManager == null)
                vmOdds.SiteManager = betSiteProxyService;
        }

        #endregion
    }
}
